#include "Document.h"

#include "IDocumentObjectFactory.h"

#include <QDebug>

namespace LDO
{
    class DocumentPrivate
    {
        Q_DISABLE_COPY(DocumentPrivate)
        Q_DECLARE_PUBLIC(Document)
        Document * const q_ptr;

        explicit DocumentPrivate(Document *document):
            q_ptr(document)
        {}

        QMap<QUuid, IDocumentObject*> objects;
        QList<IDocumentObjectFactory*> factories;
        QMap<int, IDocumentObjectFactory*> typeToFactory;
        QMap<QString, int> typeNameToTypeId;
        QMap<int, QString> typeIdToTypeName;
        QString fileName;
        QList<IDocumentObject*> selectedObjects;
    };
}

using namespace LDO;

Document::Document(QObject *parent)
    : IDocumentObject()
    , d_ptr(new DocumentPrivate(this))
{
    setParent(parent);
}

Document::~Document()
{

}

IDocumentObject *Document::createObject(const QString &typeName)
{
    Q_D(Document);

    if (!d->typeNameToTypeId.contains(typeName))
    {
        qWarning() << "Document::addObject: Unknown object type name:" << typeName;
        return nullptr;
    }

    return createObject(d->typeNameToTypeId[typeName]);
}

IDocumentObject *Document::createObject(int typeId)
{
    Q_D(Document);

    if (!d->typeIdToTypeName.contains(typeId))
    {
        qWarning() << "Document::addObject: Unknown object type id:" << typeId;
        return nullptr;
    }
    const QString typeName = d->typeIdToTypeName[typeId];

    IDocumentObject *object = d->typeToFactory[typeId]->createObject(typeId);

    if (object == nullptr)
    {
        qWarning() << "Document::addObject: Failed to create object of type" << typeName;
        return nullptr;
    }

    static int counter = 0;
    object->setObjectName(QString("%1%2").arg(typeName).arg(counter++));

    return object;

}

IDocumentObject *Document::addObject(const QString &typeName)
{
    IDocumentObject *object = createObject(typeName);

    if (object != nullptr)
        addChildObject(object);

    return object;
}

IDocumentObject *Document::addObject(int typeId)
{
    IDocumentObject *object = createObject(typeId);

    if (object != nullptr)
        addChildObject(object);

    return object;
}

QString Document::iconName(const IDocumentObject *object) const
{
    Q_D(const Document);

    if (object == nullptr)
        return QString();

    int typeId = QMetaType::type(QString("%1*").arg(object->metaObject()->className()).toUtf8().constData());
    if (typeId == QMetaType::UnknownType)
        return QString();

    if (!d->typeToFactory.contains(typeId))
        return QString();

    return d->typeToFactory.value(typeId)->iconName(typeId, object);
}

void Document::setSelectedObjects(const QList<IDocumentObject *> &selectedObjects)
{
    Q_D(Document);
    auto previous = d->selectedObjects;
    d->selectedObjects = selectedObjects;
    emit selectedObjectsChanged(d->selectedObjects, previous);
}

const QList<IDocumentObject *> Document::selectedObjects() const
{
    Q_D(const Document);
    return d->selectedObjects;
}

void Document::clearSelectedObjects()
{
    Q_D(Document);
    if (d->selectedObjects.isEmpty())
        return;
    setSelectedObjects(QList<IDocumentObject *>());
}

bool Document::registerFactory(IDocumentObjectFactory *factory)
{
    Q_D(Document);

    if (factory == nullptr)
    {
        qWarning() << "Document::registerFactory: Attempting to register a nullptr factory";
        return false;
    }

    if (d->typeToFactory.values().contains(factory))
    {
        qWarning() << "Document::registerFactory: factory already registered";
        return false;
    }

    for (int typeId: factory->objectTypeIds())
    {
        const QString typeName = factory->typeName(typeId);
        if (d->typeIdToTypeName.contains(typeId))
        {

            qWarning() << "Document::registerFactory: Another factory is already registered for type id:" << typeId;
            continue;
        }
        if (d->typeNameToTypeId.contains(typeName))
        {

            qWarning() << "Document::registerFactory: Another factory is already registered for type name:" << typeName;
            continue;
        }

        d->factories.append(factory);
        d->typeToFactory.insert(typeId, factory);
        d->typeIdToTypeName[typeId] = typeName;
        d->typeNameToTypeId[typeName] = typeId;
    }
    return true;
}

QString Document::fileName() const
{
    Q_D(const Document);

    return d->fileName;
}

void Document::setFileName(QString fileName)
{
    Q_D(Document);

    if (d->fileName == fileName)
        return;

    d->fileName = fileName;
    emit fileNameChanged(d->fileName);
}
